前面幾天,我們已經把 PowerShell 的基礎慢慢串起來了。
從:
變數
↓
Array
↓
foreach
↓
if
↓
try / catch
↓
CSV
↓
Log
到 Day 6,我們甚至已經可以對多台 Server 做連線檢查,並把錯誤寫進報表。
但前面有一件事情其實一直沒有真的做到。
我們一直說:
Server Check
但其實目前真正檢查的只有:
ICMP Ping
也就是:
這台主機有沒有回應?
這跟:
這台 Server 現在是不是健康?
其實是兩件完全不同的事情。
所以 Day 7 開始,我們正式進入 Windows Server 維運。
今天的目標是取得:
CPU
Memory
Disk
最後做出一份像這樣的健康檢查結果:
Computer : SERVER01
CPU : 22 %
Memory : 68 %
Disk C: : 71 %
Disk D: : 45 %
Status : Healthy
Server 可以 Ping,不代表它是健康的
假設:
Test-Connection SERVER01 -Count 1 -Quiet
回傳:
True
只能證明:
SERVER01 目前有回 ICMP。
但這台 Server 可能同時是:
CPU 99%
Memory 97%
Disk C: 99%
甚至某個重要 Service 已經停止。
所以:
Ping Success
只能代表:
網路層面有回應。
不能直接等同:
Server Healthy
因此真正做巡檢時,我們通常需要同時看多個指標。
今天第一次使用 Get-CimInstance
今天會開始使用一個 Windows 系統管理非常實用的 Cmdlet:
Get-CimInstance
它可以取得 Windows 系統裡許多資訊。
例如:
Get-CimInstance Win32_OperatingSystem
可能會得到:
SystemDirectory : C:\Windows\system32
Organization :
BuildNumber : 26100
RegisteredUser : Windows User
SerialNumber : ...
Version : 10.0.26100
再例如:
Get-CimInstance Win32_Processor
可以取得 CPU 資訊。
還有:
Get-CimInstance Win32_LogicalDisk
取得磁碟資料。
可以先把它理解成:
PowerShell 向 Windows 詢問系統目前的狀態。
第一個檢查:CPU
先輸入:
Get-CimInstance Win32_Processor
通常會看到:
Caption
DeviceID
Manufacturer
MaxClockSpeed
Name
SocketDesignation
但是我們現在想知道的是:
CPU 使用率。
可以使用:
Get-CimInstance Win32_Processor |
Select-Object Name, LoadPercentage
例如:
Name LoadPercentage
Intel(R) Xeon(R) CPU E5-2680 v4 23
也就是:
CPU Load = 23%
如果只想拿數字:
$CPU = Get-CimInstance Win32_Processor |
Measure-Object -Property LoadPercentage -Average
然後:
$CPU.Average
可能得到:
23
我們也可以直接:
$CPUUsage = (
Get-CimInstance Win32_Processor |
Measure-Object -Property LoadPercentage -Average
).Average
現在:
$CPUUsage
就是目前取得的 CPU Load。
CPU 數字要怎麼判斷?
先假設我們自己定一個簡單門檻:
0~79% → Normal
80~89% → Warning
90%以上 → Critical
PowerShell 可以寫:
if ($CPUUsage -ge 90) {
$CPUStatus = "Critical"
}
elseif ($CPUUsage -ge 80) {
$CPUStatus = "Warning"
}
else {
$CPUStatus = "Normal"
}
這裡第一次出現:
elseif
意思就是:
如果前面的條件不符合,再檢查下一個條件。
CPU 80% 就一定有問題嗎?
也不是。
例如 Server 剛好正在:
跑 Backup
掃描防毒
處理大量資料
跑批次工作
Windows Update
CPU 瞬間升到 90%,不一定代表異常。
所以這裡的 Threshold 只是:
用來快速找出值得注意的 Server。
真正判斷異常通常還要考慮:
持續多久?
平常使用率多少?
當時跑什麼 Process?
是否影響 Service?
這跟前面 Service 巡檢的觀念一樣:
Automation 幫我們縮小範圍,不是直接替工程師下結論。
第二個檢查:Memory
Windows 記憶體資訊可以從:
Get-CimInstance Win32_OperatingSystem
取得。
先看:
Get-CimInstance Win32_OperatingSystem |
Select-Object TotalVisibleMemorySize, FreePhysicalMemory
可能得到:
TotalVisibleMemorySize FreePhysicalMemory
16712124 5231000
這裡的單位通常是:
KB
直接看其實不太直覺。
我們真正想要的是:
總 Memory
使用 Memory
Memory 使用率
計算 Memory 使用率
先取得資料:
$OS = Get-CimInstance Win32_OperatingSystem
總記憶體:
$TotalMemory = $OS.TotalVisibleMemorySize
可用記憶體:
$FreeMemory = $OS.FreePhysicalMemory
已使用:
$UsedMemory = $TotalMemory - $FreeMemory
最後計算百分比:
$MemoryUsage = math::Round(
($UsedMemory / $TotalMemory) * 100,
2
)
例如結果:
68.42
代表:
Memory Usage = 68.42%
math::Round 是什麼?
這裡看到:
它是拿來四捨五入。
假設:
68.42873491
我們不需要報表顯示那麼多小數。
所以:
math::Round(68.42873491, 2)
結果:
68.43
維運報表通常會好看很多。
順便把 Memory 轉成 GB
總記憶體:
$TotalMemoryGB = math::Round(
$OS.TotalVisibleMemorySize / 1MB,
2
)
這裡可能會讓人疑惑:
1MB
為什麼 TotalVisibleMemorySize 是 KB,卻除以 1MB?
因為 PowerShell 的:
1KB
1MB
1GB
本身都是數值常數。
但為了避免初學時看起來太混亂,也可以寫得更直白:
$TotalMemoryGB = math::Round(
$OS.TotalVisibleMemorySize / 1024 / 1024,
2
)
例如:
15.94 GB
可用記憶體:
$FreeMemoryGB = math::Round(
$OS.FreePhysicalMemory / 1024 / 1024,
2
)
Memory 也設定 Threshold
例如:
< 80% Normal
80~89% Warning
= 90% Critical
可以寫:
if ($MemoryUsage -ge 90) {
$MemoryStatus = "Critical"
}
elseif ($MemoryUsage -ge 80) {
$MemoryStatus = "Warning"
}
else {
$MemoryStatus = "Normal"
}
後面我們就可以直接看到:
Memory : 68% → Normal
而不是自己每次看數字再判斷。
第三個檢查:Disk
磁碟空間幾乎是系統維運一定會檢查的項目。
因為很多 Server 問題最後會發現:
C: 滿了
Log 撐爆
Backup 沒清
Temp 一直增加
Database File 長大
我們可以使用:
Get-CimInstance Win32_LogicalDisk
先看看資料。
只取本機磁碟
Win32_LogicalDisk 不一定只有本機硬碟。
所以可以加:
DriveType = 3
查詢:
Get-CimInstance Win32_LogicalDisk `
-Filter "DriveType=3"
可能看到:
DeviceID Size FreeSpace
C: 255000000000 85000000000
D: 500000000000 320000000000
這些數字都是 Bytes。
人很難直接看。
所以要轉成 GB。
計算 Disk 使用率
可以:
$Disks = Get-CimInstance Win32_LogicalDisk `
-Filter "DriveType=3"
接著:
foreach ($Disk in $Disks) {
$TotalGB = [math]::Round(
$Disk.Size / 1GB,
2
)
$FreeGB = [math]::Round(
$Disk.FreeSpace / 1GB,
2
)
$UsedGB = [math]::Round(
$TotalGB - $FreeGB,
2
)
$UsedPercent = [math]::Round(
(($Disk.Size - $Disk.FreeSpace) / $Disk.Size) * 100,
2
)
Write-Host "$($Disk.DeviceID) $UsedPercent %"
}
假設:
C: 72.31 %
D: 36.08 %
現在就開始有巡檢資訊了。
建立 Disk Object
跟前面一樣,我不希望只是:
Write-Host
所以可以建立:
$DiskResult = [PSCustomObject]@{
Drive = $Disk.DeviceID
TotalGB = $TotalGB
UsedGB = $UsedGB
FreeGB = $FreeGB
UsedPercent = $UsedPercent
}
最後:
Drive TotalGB UsedGB FreeGB UsedPercent
C: 237.8 171.9 65.9 72.29
D: 465.6 168.2 297.4 36.13
這就很適合:
輸出 CSV
或送進後面的巡檢報表。
Disk Threshold 怎麼設定?
假設:
< 80% Normal
80~89% Warning
= 90% Critical
可以加入:
if ($UsedPercent -ge 90) {
$DiskStatus = "Critical"
}
elseif ($UsedPercent -ge 80) {
$DiskStatus = "Warning"
}
else {
$DiskStatus = "Normal"
}
最後:
C: 72% Normal
D: 91% Critical
工程師第一眼就知道:
D: 比較需要先看。
把 CPU、Memory、Disk 串起來
現在我們已經有三組資訊:
CPU
Memory
Disk
可以開始做第一版 Health Check。
先取得電腦名稱:
$ComputerName = $env:COMPUTERNAME
CPU
$CPUUsage = math::Round(
(
Get-CimInstance Win32_Processor |
Measure-Object -Property LoadPercentage
-Average
).Average,
2
)
Memory
$OS = Get-CimInstance Win32_OperatingSystem
$TotalMemory = $OS.TotalVisibleMemorySize
$FreeMemory = $OS.FreePhysicalMemory
$UsedMemory = $TotalMemory - $FreeMemory
$MemoryUsage = math::Round(
($UsedMemory / $TotalMemory) * 100,
2
)
Disk
$Disks = Get-CimInstance Win32_LogicalDisk `
-Filter "DriveType=3"
現在資料就都準備好了。
怎麼決定整台 Server 的 Status?
這裡開始有一個很重要的問題。
假設:
CPU Normal
Memory Normal
Disk C Normal
Disk D Critical
那整台 Server 應該顯示:
Healthy
嗎?
當然不應該。
所以我們可以定義:
只要任何一項 Critical
→ Server = Critical
沒有 Critical,但有 Warning
→ Server = Warning
全部 Normal
→ Server = Healthy
這就是最基本的 Health Check 邏輯。
建立整體 Health Status
先設定:
$OverallStatus = "Healthy"
如果 CPU Critical:
if ($CPUUsage -ge 90) {
$OverallStatus = "Critical"
}
如果 Memory:
if ($MemoryUsage -ge 90) {
$OverallStatus = "Critical"
}
Disk 也一樣。
但這樣寫久了會越來越亂。
所以今天先不急著做得非常漂亮。
Day 7 的重點還是:
把健康資料正確抓出來。
後面我們再重構。
第一版完整 Server Health Check
下面是一個可以直接拿來練習的版本:
$ComputerName = $env:COMPUTERNAME
$CheckTime = Get-Date
$CPUUsage = math::Round(
(
Get-CimInstance Win32_Processor |
Measure-Object -Property LoadPercentage
-Average
).Average,
2
)
$OS = Get-CimInstance Win32_OperatingSystem
$TotalMemory = $OS.TotalVisibleMemorySize
$FreeMemory = $OS.FreePhysicalMemory
$UsedMemory = $TotalMemory - $FreeMemory
$MemoryUsage = math::Round(
($UsedMemory / $TotalMemory) * 100,
2
)
$TotalMemoryGB = math::Round(
$TotalMemory / 1024 / 1024,
2
)
$FreeMemoryGB = math::Round(
$FreeMemory / 1024 / 1024,
2
)
if ($CPUUsage -ge 90) {
$CPUStatus = "Critical"
}
elseif ($CPUUsage -ge 80) {
$CPUStatus = "Warning"
}
else {
$CPUStatus = "Normal"
}
if ($MemoryUsage -ge 90) {
$MemoryStatus = "Critical"
}
elseif ($MemoryUsage -ge 80) {
$MemoryStatus = "Warning"
}
else {
$MemoryStatus = "Normal"
}
$Summary = [PSCustomObject]@{
ComputerName = $ComputerName
CheckTime = $CheckTime
CPUUsage = $CPUUsage
CPUStatus = $CPUStatus
TotalMemoryGB = $TotalMemoryGB
FreeMemoryGB = $FreeMemoryGB
MemoryUsage = $MemoryUsage
MemoryStatus = $MemoryStatus
}
$Summary
輸出可能像:
ComputerName : SERVER01
CheckTime : 2026/09/15 05:10:00
CPUUsage : 18
CPUStatus : Normal
TotalMemoryGB : 15.94
FreeMemoryGB : 5.24
MemoryUsage : 67.13
MemoryStatus : Normal
再把 Disk 加進來
因為一台 Server 可能有:
C:
D:
E:
F:
Disk 跟 CPU、Memory 不太一樣。
CPU 通常是一個整體結果。
Memory 也是一個結果。
Disk 則可能:
一台 Server
↓
多個 Drive
所以 Disk 比較適合另外建立一組 Object。
$DiskResults = @()
$Disks = Get-CimInstance Win32_LogicalDisk `
-Filter "DriveType=3"
foreach ($Disk in $Disks) {
$TotalGB = [math]::Round(
$Disk.Size / 1GB,
2
)
$FreeGB = [math]::Round(
$Disk.FreeSpace / 1GB,
2
)
$UsedPercent = [math]::Round(
(($Disk.Size - $Disk.FreeSpace) / $Disk.Size) * 100,
2
)
if ($UsedPercent -ge 90) {
$DiskStatus = "Critical"
}
elseif ($UsedPercent -ge 80) {
$DiskStatus = "Warning"
}
else {
$DiskStatus = "Normal"
}
$DiskResult = [PSCustomObject]@{
ComputerName = $ComputerName
CheckTime = $CheckTime
Drive = $Disk.DeviceID
TotalGB = $TotalGB
FreeGB = $FreeGB
UsedPercent = $UsedPercent
Status = $DiskStatus
}
$DiskResults += $DiskResult
}
最後:
$DiskResults
可能得到:
ComputerName Drive TotalGB FreeGB UsedPercent Status
SERVER01 C: 237.8 65.9 72.29 Normal
SERVER01 D: 465.6 42.1 90.96 Critical
這時候問題就非常明顯了。
做成 CSV 報表
延續前幾天的做法:
$ReportFolder = "C:\Temp"
確認資料夾:
if (-not (Test-Path $ReportFolder)) {
New-Item `
-Path $ReportFolder `
-ItemType Directory |
Out-Null
}
日期:
$Date = Get-Date -Format "yyyyMMdd"
Summary:
$Summary |
Export-Csv -Path "$ReportFolder\Server_Summary_$Date.csv"
-NoTypeInformation `
-Encoding UTF8
Disk:
$DiskResults |
Export-Csv -Path "$ReportFolder\Server_Disk_$Date.csv"
-NoTypeInformation `
-Encoding UTF8
最後會得到:
C:\Temp
│
├── Server_Summary_20260915.csv
│
└── Server_Disk_20260915.csv
為什麼先拆成兩份報表?
可能有人會問:
為什麼不把 CPU、Memory、C:、D: 全部塞在同一列?
當然可以。
例如:
Server CPU Memory C_Disk D_Disk
但這種寫法有一個問題。
今天:
SERVER01
有:
C:
D:
SERVER02 可能有:
C:
D:
E:
F:
那報表欄位就會越來越難固定。
所以目前我比較傾向:
Summary Report
負責:
Server
CPU
Memory
Status
而:
Disk Report
一個 Drive 一筆。
例如:
Server Drive UsedPercent
SERVER01 C: 72%
SERVER01 D: 91%
SERVER02 C: 45%
SERVER02 D: 62%
SERVER02 E: 82%
後面要篩:
UsedPercent >= 80
也非常容易。
今天的 Threshold 只是範例
今天使用:
CPU
80% Warning
90% Critical
Memory
80% Warning
90% Critical
Disk
80% Warning
90% Critical
這些數字不是所有公司都必須這樣設定。
實際環境要看:
系統用途
Server 規格
Baseline
Application 特性
SLA
歷史使用狀況
例如 Database Server 平常 Memory 使用率就可能很高。
如果:
Memory > 80%
就一直告警,最後反而變成 Alert Fatigue。
所以真正成熟的 Monitoring 不只是:
設一個百分比。
還需要知道:
這台系統正常的樣子是什麼?
CPU 的 LoadPercentage 也不是精密效能監控
今天使用:
Win32_Processor.LoadPercentage
非常適合拿來做:
簡單巡檢
快速 Snapshot
基本健康報表
但如果要做真正持續性的 Performance Monitoring,我不會只靠它。
因為:
它比較適合當某個時間點的觀察值,不等於完整的效能監控歷史。
真正要分析:
CPU 什麼時候升高?
持續了幾分鐘?
每天固定哪個時間?
跟哪個 Process 有關?
會更適合使用:
Performance Counter
Monitoring System
Prometheus
Grafana
SCOM
Zabbix
今天的目標不是取代 Monitoring System。
而是做:
PowerShell Daily Health Check。
把它想成「每日巡檢」
假設以前每天早上要:
RDP Server01
看 Task Manager
看 Memory
打開 C:
看 Disk
RDP Server02
再做一次
RDP Server03
再做一次
現在可以慢慢變成:
PowerShell
↓
取得 CPU
↓
取得 Memory
↓
取得 Disk
↓
套用 Threshold
↓
產生報表
工程師只需要先看:
Warning
Critical
而不是每台都人工檢查一次。
這才是這個系列想做的事情。
如果 CPU 真的很高,下一步是什麼?
假設今天報表:
SERVER01
CPU = 96%
Status = Critical
我們下一個問題一定會是:
到底是哪一個 Process 在吃 CPU?
PowerShell 其實已經可以:
Get-Process |
Sort-Object CPU -Descending |
Select-Object -First 10 `
ProcessName,
Id,
CPU
這跟 Day 2 學的內容又接回來了。
未來我們甚至可以做到:
CPU > 90%
↓
自動取得 Top Process
↓
寫入 Incident Report
這就開始從:
Monitoring
慢慢走向:
Diagnostics
Day 7 小結
今天我們正式開始 Windows Server Health Check。
第一次取得三個真正跟系統健康比較有關係的資訊:
CPU
Memory
Disk
主要使用:
Get-CimInstance Win32_Processor
Get-CimInstance Win32_OperatingSystem
Get-CimInstance Win32_LogicalDisk
再搭配前幾天學過的:
變數
foreach
if / elseif
PSCustomObject
Export-Csv
整個流程現在已經變成:
Windows Server
↓
Get-CimInstance
↓
CPU / Memory / Disk
↓
計算 %
↓
Threshold
↓
Normal / Warning / Critical
↓
PSCustomObject
↓
CSV Report
這就是第一版的:
Windows Server Daily Health Check。
而且我認為今天有一個觀念比 Script 本身更重要:
一個 Server 能 Ping,不代表 Server 是健康的。
健康狀態需要靠多個觀察點一起判斷。
Day 8 預告
Day 8|Windows Server 巡檢進階:Service、Uptime 與最後開機時間
今天我們檢查的是:
CPU
Memory
Disk
但 Server 就算這三項全部正常,也可能有問題。
例如:
重要 Service 已停止
Server 剛剛才重開機
某台 Server 已經 300 天沒重開
Windows Update 重啟過
所以 Day 8 我們會把巡檢內容增加到:
CPU
Memory
Disk
Service
Uptime
Last Boot Time
並開始把巡檢結果整理成更完整的:
SERVER01
CPU Normal
Memory Normal
Disk Warning
Service Normal
Uptime 32 Days
Overall Warning
從 Day 8 開始,我們會慢慢把前幾天零散的 PowerShell 技巧,組成一支真正可以持續擴充的 Windows Server Health Check Script。